package com.aerors.DataProcess;

import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javax.imageio.ImageIO;

import org.gdal.gdal.Dataset;
import org.gdal.gdal.gdal;
import org.gdal.gdalconst.gdalconst;
import org.gdal.gdalconst.gdalconstConstants;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.referencing.CRS;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.NoSuchAuthorityCodeException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.aerors.Util.GeoUtils;
import com.aerors.Util.ImageUtils;

/**
 * 根据TMS规范对影像数据进行切片
 * @author Administrator
 *
 */
public class ImageTiler {
	static Logger logger = LoggerFactory.getLogger(ImageTiler.class);

	private static DecimalFormat df = new DecimalFormat("#.000000000");

	public static void main(String[] args) throws Exception {
		ImageTiler app =new ImageTiler();
		app.tiler("C:\\data\\dem\\ASTGTM_N33E099Q.tif", 5, 9,"C:\\data\\dem\\tiles");
	}

	public void tiler(String imagePath,int startZoom,int endZoom,String outDir) throws Exception {
		gdal.AllRegister();

		String fileName = imagePath;

		// 读取影像数据
		Dataset dataset = gdal.Open(fileName, gdalconstConstants.GA_ReadOnly);
		if (dataset == null) {
			System.err.println("GDALOpen failed - " + gdal.GetLastErrorNo());
			System.err.println(gdal.GetLastErrorMsg());

			System.exit(1);
		}
		int xCount = dataset.getRasterXSize();
		int yCount = dataset.getRasterYSize();

		String imageWKID = ImageUtils.getImageWKID(dataset);
		// 获取原图的地理坐标信息
		double[] ori_transform = dataset.GetGeoTransform();
		double latMin = 0, latMax = 0, lonMin = 0, lonMax = 0;
		System.out.println(String.format("Origin = (%s, %s)", ori_transform[0], ori_transform[3]));
		System.out.println(String.format("Pixel Size = (%s, %s)", ori_transform[1], ori_transform[5]));

		if (imageWKID.equals("EPSG:4326")) {
			latMin = ori_transform[3];
			latMax = ori_transform[3] + (yCount * ori_transform[1]);
			lonMin = ori_transform[0];
			lonMax = ori_transform[0] + (xCount * ori_transform[1]);
		} else if (imageWKID.equals("EPSG:3857")) {
			double[] lonlatUpLeft = GeoUtils.WebMercator2lonLat(ori_transform[0], ori_transform[3]);
			double[] lonlatRightBottom = GeoUtils.WebMercator2lonLat(ori_transform[0] + (xCount * ori_transform[1]),
					ori_transform[3] + (yCount * ori_transform[5]));

			lonMin = lonlatUpLeft[0];
			latMax = lonlatUpLeft[1];

			lonMax = lonlatRightBottom[0];
			latMin = lonlatRightBottom[1];
		}

		// 原始图像东西方向像素分辨率
		double src_w_e_pixel_resolution = (lonMax - lonMin) / xCount;
		// 原始图像南北方向像素分辨率
		double src_n_s_pixel_resolution = (latMax - latMin) / yCount;

		CoordinateReferenceSystem crs = null;
		try {
			crs = CRS.decode("EPSG:4326");
		} catch (NoSuchAuthorityCodeException e) {
			e.printStackTrace();
		} catch (FactoryException e) {
			e.printStackTrace();
		}
		ReferencedEnvelope imageBound = new ReferencedEnvelope(lonMin, lonMax, latMin, latMax, crs);

		for (int zoom = startZoom; zoom <= endZoom; zoom++) {
			int tileRowMax = GeoUtils.getOSMTileYFromLatitude(latMin, zoom);
			int tileRowMin = GeoUtils.getOSMTileYFromLatitude(latMax, zoom);
			int tileColMin = GeoUtils.getOSMTileXFromLongitude(lonMin, zoom);
			int tileColMax = GeoUtils.getOSMTileXFromLongitude(lonMax, zoom);

			// 遍历行列号,获取切片文件
			for (int row = tileRowMin; row <= tileRowMax; row++) {
				for (int col = tileColMin; col <= tileColMax; col++) {
					double tempLatMin = GeoUtils.tile2lat(row + 1, zoom);
					double tempLatMax = GeoUtils.tile2lat(row, zoom);

					double tempLonMin = GeoUtils.tile2lon(col, zoom);
					double tempLonMax = GeoUtils.tile2lon(col + 1, zoom);

					ReferencedEnvelope tileBound = new ReferencedEnvelope(tempLonMin, tempLonMax, tempLatMin,
							tempLatMax, crs);
					ReferencedEnvelope intersect = tileBound.intersection(imageBound);
					
					if(intersect.getArea() > 0) {
						// 切片东西方向像素分辨率
						double dst_w_e_pixel_resolution = (tempLonMax - tempLonMin) / 256;
						// 切片南北方向像素分辨率
						double dst_n_s_pixel_resolution = (tempLatMax - tempLatMin) / 256;

						// 求切图范围和原始图像交集的起始点像素坐标
						int offset_x = (int) ((intersect.getMinX() - lonMin) / src_w_e_pixel_resolution);
						int offset_y = (int) Math.abs((intersect.getMaxY() - latMax) / src_n_s_pixel_resolution);

						// 求在切图地理范围内的原始图像的像素大小
						int block_xsize = (int) ((intersect.getMaxX() - intersect.getMinX()) / src_w_e_pixel_resolution);
						int block_ysize = (int) ((intersect.getMaxY() - intersect.getMinY()) / src_n_s_pixel_resolution);
						
						// 求原始图像在切片地理范围内的像素大小
						int image_Xbuf = (int) Math
								.ceil((intersect.getMaxX() - intersect.getMinX()) / dst_w_e_pixel_resolution);
						int image_Ybuf = (int) Math
								.ceil(Math.abs((intersect.getMaxY() - intersect.getMinY()) / dst_n_s_pixel_resolution));
						
						// 求原始图像在切片中的偏移坐标
						int imageOffsetX = (int) ((intersect.getMinX() - tempLonMin) / dst_w_e_pixel_resolution);
						int imageOffsetY = (int) Math.abs((intersect.getMaxY() - tempLatMax) / dst_n_s_pixel_resolution);
						imageOffsetX = imageOffsetX > 0 ? imageOffsetX : 0;
						imageOffsetY = imageOffsetY > 0 ? imageOffsetY : 0;
						
						int bandCount = dataset.GetRasterCount();
						int[] bandBuffData = new int[image_Xbuf * image_Ybuf * bandCount];
						dataset.ReadRaster(offset_x, offset_y, block_xsize, block_ysize, image_Xbuf, image_Ybuf,
									gdalconst.GDT_Int32, bandBuffData, new int[] {1});

						// 绘制图片
						BufferedImage pngImage = new BufferedImage(256, 256, BufferedImage.TYPE_INT_ARGB);
						int drawStartX = imageOffsetX;
						int drawStartY = imageOffsetY;
						int drawEndX = drawStartX + image_Xbuf;
						int drawEndY = drawStartY + image_Ybuf;
						
//						int index = 0;
//						for (int j = drawStartY; j < drawEndY; j++) {
//							for (int i = drawStartX; i < drawEndX; i++) {
//								int elevation = bandBuffData[index];
//								int[] rgb = this.calcRGB(elevation);
//								Color c = new Color(rgb[0],rgb[1],rgb[2]);
//								pngImage.setRGB(i, j, c.getRGB());
//								index++;
//							}
//						}
						
						long start = System.currentTimeMillis();
						int threadNum = 10;
						int count = image_Xbuf * image_Ybuf;
						int blockSize = count / threadNum;
						List<TaskParam> taskList = new ArrayList<>();
						int index = 0;
						for (int j = drawStartY; j < drawEndY; j++) {
							for (int i = drawStartX; i < drawEndX; i++) {
								TaskParam taskParam = new TaskParam(index, i, j);
								taskList.add(taskParam);
								index++;
							}
						}
						ExecutorService pool = Executors.newFixedThreadPool(threadNum);
						for (int i = 0; i < threadNum; i++) {
							int tastStart = i*blockSize;
							int taskEnd = tastStart + blockSize > count ? count : tastStart + blockSize;
							List<TaskParam> list = taskList.subList(tastStart, taskEnd);
							calcRGBThread t = new calcRGBThread(list, pngImage, bandBuffData);
							pool.submit(t);
						}
						pool.shutdown();
						
						while(true) {
							if(pool.isTerminated()) {
								System.out.println("All finished");
								break;
							}
						}
						long end = System.currentTimeMillis();
						System.out.println("cost:" + (end -start) + "ms");
						
						System.out.println("current processing -----> zoom:" + zoom + ";col:" + col + ";row:" + row);
						File outFile = new File(outDir + File.separator + zoom + File.separator + col + File.separator + row + ".png");
						System.out.println("outFile:" + outFile.getAbsolutePath());
						if(!outFile.exists()) {
							outFile.getParentFile().mkdirs();
						}
						ImageIO.write(pngImage, "png", outFile);
					}
					
				}
			}
		}

		dataset.delete();
		gdal.GDALDestroyDriverManager();
	}

	public int[] calcRGB(float elevation) {
		int[] rgb = new int[3];
		for (int r = 0; r < 256; r++) {
			for (int g = 0; g < 256; g++) {
				for (int b = 0; b < 256; b++) {
					if ((r * 256 * 256 + g * 256 + b) == (elevation + 10000) * 10) {
						rgb[0] = r;
						rgb[1] = g;
						rgb[2] = b;
						break;
					}
				}
			}
		}
		return rgb;
	}
	
	/**
	 * InputStream转byte数组
	 * 
	 * @param input
	 * @return
	 * @throws IOException
	 */
	public static byte[] toByteArray(InputStream input) throws IOException {
		ByteArrayOutputStream output = new ByteArrayOutputStream();
		byte[] buffer = new byte[4096];
		int n = 0;
		while (-1 != (n = input.read(buffer))) {
			output.write(buffer, 0, n);
		}
		return output.toByteArray();
	}

}

class TaskParam {
	private int index;
	private int col;
	private int row;
	public TaskParam(int index, int col, int row) {
		super();
		this.index = index;
		this.col = col;
		this.row = row;
	}
	public int getIndex() {
		return index;
	}
	public void setIndex(int index) {
		this.index = index;
	}
	public int getCol() {
		return col;
	}
	public void setCol(int col) {
		this.col = col;
	}
	public int getRow() {
		return row;
	}
	public void setRow(int row) {
		this.row = row;
	}
	
}

class calcRGBThread implements Runnable {
	private List<TaskParam> taskList;
	private BufferedImage pngImage;
	private int[] bufferData;

	public calcRGBThread(List<TaskParam> taskList, BufferedImage pngImage, int[] bufferData) {
		super();
		this.taskList = taskList;
		this.pngImage = pngImage;
		this.bufferData = bufferData;
	}

	public int[] calcRGB(float elevation) {
		int[] rgb = new int[3];
		for (int r = 0; r < 256; r++) {
			for (int g = 0; g < 256; g++) {
				for (int b = 0; b < 256; b++) {
					if ((r * 256 * 256 + g * 256 + b) == (elevation + 10000) * 10) {
						rgb[0] = r;
						rgb[1] = g;
						rgb[2] = b;
						break;
					}
				}
			}
		}
		return rgb;
	}

	@Override
	public void run() {
		for(TaskParam param : taskList) {
			int elevation = bufferData[param.getIndex()];
			int[] rgb = this.calcRGB(elevation);
//			System.out.println("elevation=" + elevation +";r=" + rgb[0] + ";g=" + rgb[1] + ";b=" + rgb[2]);
			Color c = new Color(rgb[0],rgb[1],rgb[2]);
			pngImage.setRGB(param.getCol(), param.getRow(), c.getRGB());
		}
	}
}